#ifndef _ECONOMY_H_
#define _ECONOMY_H_

#include <pthread.h>
#include "interpret.h"
#include "cover.h"
#include "nolock.h"
#include "objectstore.h"

// This structure is meant to assign credit for improvements to a utility
// score based on the utility gains and how long it has been taking to 
// produce such gains.  
// In particular, we take the most generous view of how long it takes to 
// make this improvement based on a line between the current point on the
// utility/time graph (post-improvement) and any point in the past.
struct CreditAssigner {
  // input a new utility at a new time.
  // return an amount of credit to assign in units of time.
  // call this at time 0 with initial utility - will return 0.0
  // call again whenever there is an improvement in the maximum utility
  double Credit(double time, double utility, bool update);
  
  void Initialize(double time, double utility);

  // the convex hull - pairs of time, utility
  vector<pair<double, double> > history_;
  
  uint64 history_midpoint_;
  double k_history_midpoint_ratio_;
};

// This is the symbol CODE used as an LHS for runnable entities
extern Object * CODE;

// Accessor functions on Objects that represent Entities
enum EntityField {
  CONTRIBUTORS_FIELD,
  LHS_FIELD,
  RHS_FIELD,
  TOTAL_DEMAND_FIELD,
  SELF_DEMAND_FIELD,
  CREATION_TIME_FIELD,
  NUM_ENTITY_FIELDS,
};

inline Object* Contributors(Object *e) { return e->Ref(0); }
inline Object* LHS(Object *e) { return e->Ref(1); }
inline Object* RHS(Object *e) { return e->Ref(2); }
inline Object* TotalDemand(Object *e) { return e->Ref(3); }
inline Object* SelfDemand(Object *e) { return e->Ref(4); }
inline Object* CreationTime(Object *e) { return e->Ref(5); }
inline bool IsCode(Object * e) { return LHS(e) == CODE; }

// Making a prototype of an entity
inline Object * MakePrototype(ThreadAllocator * ta, Object ** contributors, 
			      Object ** lhs, Object ** rhs, 
			      Object ** total_demand, Object ** self_demand,
			      Object ** creation_time) {
  return 
    ta->MakeArray(NUM_ENTITY_FIELDS, 
		  contributors, lhs, rhs, 
		  total_demand, self_demand, creation_time);
}

enum AdType {
  AD_PERMANENT,
  AD_TEMPORARY, 
};

// Stuff related to an adstore.
struct AdStore {
  /**/ AdStore(uint64 size) : h_(size, false) {Clear();}
  void Insert(uint64 lhs_hash, Object *ad);
  Object *GetAd(uint64 lhs_hash, uint64 index);
  Object *Sample(uint64 lhs_hash, RNG *rng);
  uint64 NumAds(uint64 lhs_hash);
  // a range of pairs with the same LHS
  void BatchInsert(pair<uint64, Object *> * p, uint64 length);
  void Clear();
  uint64 Size() const { return h_.Size(); }
  NLHash<Object *> h_;
};

struct EconomyThread;

// This contains all the information necessary to run the economy
struct Economy : public AllocatorDependant {
  static void Init();
  Economy();

  // An entity is an Object * representing a mapping from an LHS -> RHS 
  // --- (LHS, RHS, CONTRIBUTORS, TOTAL_DEMAND, SELF_DEMAND)
  // Entities are kept unique by hash. A function is provided to map
  // between an Object* representing a Prototype to a real Entity

  AdStore * ads_[2]; // permanent and temporary ads.
  
  // These functions are called during garbage collection.   
  // Let's keep them here for now rather than moving them to EconomyThread
  
  void FlagReferences(int32 shard, Frontier * frontier);
  void ObjectsDeleted(int32 shard);
  int32 NumShards();
  
  void ClearTemporaryAds();


  // For testing actors on covers
  // Not using this for sequences
  // map<Solution *, vector<Object *> > solution_top_contributors_;

  int64 total_cycles_;
  int64 fake_cycles_;

  //  Necessary for balancing permanent versus temporary reading
  // Last time we cleared the temporaries
  int64 last_temporary_clear_cycles_;

  // Sum of all incomed ads in units of kCPUPerIncome
  // int64 total_income_;

  // SYNCHRONIZATION
  vector<EconomyThread *> et_;
  uint64 num_threads_;
  bool sorting_ads_;

  void MakeThreads(int n);
  void StartThreads();

  // Testing
  Tester * tester_;
  double utility_;
  CreditAssigner assigner_;
  pthread_mutex_t credit_lock_;

  // Storage
  pthread_mutex_t store_lock_;
  const char * ObjectStoreFileName() { return "store/objects";}
  const char * IncomeStoreFileName() { return "store/income";}
  ObjectStore object_store_;
  ofstream income_store_;
};
extern Economy * G_EC;

struct EconomyThread : public System, public AllocatorDependant { 
  EconomyThread(Economy *ec);

  void FlagReferences(int32 shard, Frontier * frontier);
  void ObjectsDeleted(int32 shard);
  int32 NumShards() { return 1; }

  int64 Payout();
  void OutputEconomy(ostream & out, int64 income_limit);
  void DebugOutputEconomy(ostream & out);

  string EntityToString(Object *& entity);
  
  bool IsNativeMethod(Object * name);
  Object * Syscall(int32 num_parameters);

  void ForgetSince(int64 old_clock);

  // 0 means loop forever
  void Loop(int64 max_cpu = 0); 
  void UpdateReferenceCounts(int delta);

  void REPL(istream & in);
  
  // Interacting with entities
  Object * ReadCreateEntity(istream & in);
  void ReadInitialEntities(string filename);

  // Picking a random number of cpu cycles to run something for. 
  int64 RandomCPU();
  
  //   void AddIncome(Object *& prototype, int64 income);
  bool TemporaryAdsTooFull();
  bool PermanentAdsTooFull();
  void CheckClearTemporaryAds();
  void CheckReloadPermanentAds();
  void PostTemporaryAd(Object * entity, uint64 cpu);
  void PostPermanentAd(Object * entity, uint64 cpu);
  void SortAdsQ();
  void StoreIncome(Object *entity, double cpu);
  void LoadState();
  uint64 LoadIncome(); // returns time of last entity
  
  // Reading from incomed entities
  double AdjustTemporary(double temp_cpu);
  Object *ReadAd(Object * LHS);
  Object *ReadTemporaryAd(Object * LHS);
  Object *ReadPermanentAd(Object * LHS);

  // We keep this here even though it refers to entities
  Object * RunEntity(Object * chosen_entity = NULL);
  // does some periodic tasks
  void Epoch();

  void ClearContributors();
  void AddContributor(Object *&c);

  /*
  // adds to target o and all descendants of o
  void ExpandContributors(Object *o, set<Object *> *target);

  void ExpandContributorsUnequal(Object *o, map<Object *, uint64> *target, 
				 double total, double top_fraction);
  

  // flattens contributors_
  void GetCurrentContributors(set<Object *> *ret);
  */

  // system calls

  // make a new entity on a list
  // return the entity
  Object * Post(Object *& lhs, 
		Object *& rhs, 
		Object *& self_demand,
		bool credit);

  Object * Data(Object *& param);

  // returns the additional income per contributor
  // Object * TestCover(Object *code);
  Object * Test(Object *& code);

  // ads stuff to contributor lists and only returns RHS
  Object * SyscallRead(Object *& lhs);
    
  // all direct and indirect contributors with selfdemand > 0
  // returns the total demand, or -1 if you pass maxdemand
  int64 FlattenContributors(vector<Object *> * flat);
  
  // pass in the flattened contributors
  int64 ComputeTotalDemand(const vector<Object *> & flat);  

  // DISABLED
  // For synchronizing between multiple running processes
  // ObjectStore * object_store_;

  // Get an EntityData given a prototype
  // If the contributors are themselves prototypes and not Entities
  // this will make recursive calls to make Entities for the contributors first
  //EntityData * GetCreateEntityData(Object *prototype, bool recursive = true);
  //Object * GetCreateEntity(Object *prototype) {
  //  return GetCreateEntityData(prototype)->entity_; 
  //}

  struct Testing {
  Testing(EconomyThread *t) :t_(t) {
    CHECK(!t_->in_test_);
    t_->in_test_ = true;
  }
    ~Testing() { 
      CHECK(t_->in_test_);
      t_->in_test_ = false;
    }
    EconomyThread * t_;
  };

  // data
  Economy * e_;
  Vinterpreter * vi_;
  ThreadAllocator * a_;
  uint64 id_;
  pthread_t my_thread_;
  RNG rng_;
  uint64 fake_cycles_;
  StableVector<pair<uint64, Object *> > ads_q_;
  uint64 ads_q_max_size_;
  bool in_test_;
  
  // for the current running process
  // vector of top level contributing actors.  can contain prototypes
  vector<Object *> contributors_; 
  // int64 contributors_demand_;
  vector<int64> contributor_times_;
  Object * running_;  
};

void * LoopEconomyThread(void *economy_thread);


#endif
